Skip to content

Fix Bun.inspect null deref with Proxy prototypes; stop reporting from inside the sql lazy-property builders - #30245

Closed
robobun wants to merge 23 commits into
mainfrom
farm/8928bc04/fix-sql-lazy-init-null-deref
Closed

robobun wants to merge 23 commits into
mainfrom
farm/8928bc04/fix-sql-lazy-init-null-deref

Conversation

@robobun

@robobun robobun commented May 4, 2026 •

Copy link
Copy Markdown
Collaborator

Two fuzzer findings that both surface as member call on null pointer of type 'JSC::JSCell' (JSCJSValueCell.h), plus a debug-only abort next to the second one.

1. Bun.inspect when a property read throws mid-walk

forEachPropertyImpl (bindings.cpp) walks the prototype chain with

iterating = iterating->getPrototype(globalObject).getObject();

When iterating is a Proxy whose getPrototypeOf trap throws, getPrototype returns an empty JSValue and .getObject() dereferences a null cell. The getPropertySlot(...) == false branch a few lines up has the same problem: it continued before clearing the exception, so whatever the read threw stayed pending for the rest of the walk. A Proxy get trap is one way to get there; since the WebKit bump a lazy property whose builder throws is another, and that one needs no Proxy: the Bun object's $ builder throwing during Bun.inspect(Bun) leaves its exception pending when the next property's builder is entered (the fuzzer's version of this is new DecompressionStream(globalThis) near the stack limit, where the argument error message formats globalThis).

Fix: clear the exception after getPropertySlot before the early continue, and after getPrototype (same as the fast path a few lines above), and stop the walk when the result is empty.

const o = {};
Object.setPrototypeOf(o, new Proxy({}, { getPrototypeOf() { throw 0; } }));
Bun.inspect(o);

Tests: three cases in test/js/bun/util/inspect.test.js. The two Proxy ones hit the null deref on main; the third (globalThis.Symbol = NaN; Bun.inspect(Bun) in a child) aborts a debug build of main in the next builder's exception-scope check and shows the throwing properties being skipped while the rest of the object is still printed. The child loads node:util before clobbering Symbol: on Windows Bun.env has a custom inspect, and utilInspectFunction's lazy initializer returns without setting anything when node:util fails to load, which is a separate bug (it reproduces on main on every platform with any custom-inspect object once Symbol is clobbered) and is being handled separately.

2. Bun object lazy properties whose builder throws

This is what the fuzzer was mostly hitting (Bun.$ / Bun.sql / Bun.SQL / Bun.postgres read near the stack limit, or after clobbering Symbol, and Bun.redis with an invalid REDIS_URL): the PropertyCallback returned empty with an exception pending and reifyStaticProperty put the empty value into the slot. That is fixed in the WebKit this branch now pins: reifyStaticProperty skips the put for an empty result and setUpStaticFunctionSlot reports the slot as absent with the exception still pending, so the read throws and the slot stays unreified (a later read runs the builder again; #37714 pins the redis case). Earlier revisions of this PR worked around it in the callbacks by reporting and clearing the exception and reifying undefined. With the engine fix in place that is worse than what main does (the error stops being catchable and the slot is poisoned for the rest of the process), and CI caught it in builtin-esm-lazy-exports.test.ts, so those changes are gone.

What remains is a debug-only problem in defaultBunSQLObject / constructBunSQLObject: under BUN_DEBUG they reported the exception to the uncaught handler from inside the builder, i.e. in the middle of the property lookup with the exception still pending. The reporter re-enters JS, and a debug build aborts on Structure::storedPrototype's object->structure() == this assertion (globalThis.Symbol = NaN; Bun.sql, or anything that formats the whole Bun object near the stack limit). Removing the report makes these builders behave like constructBunShell already does: the exception propagates to the read. #37258 does the same for the process.* builders in BunProcess.cpp (by deferring the report instead); the two do not overlap.

Test: test/js/bun/util/BunObject.test.ts reads $, sql, SQL and postgres twice each with Symbol clobbered and expects a TypeError every time. On a debug build of main the child aborts; it passes on release builds of main, since the report was debug-only.

Issue #19650 was the engine-level bug and is already closed, so this PR does not claim it.


no test proof · iteration 22 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/inspect.test.js

When the bun:sql internal module fails to load (e.g. stack overflow),
the PropertyCallback returned an empty JSValue. JSC's reifyStaticProperty
passes that straight to putDirect without checking for exceptions, which
dereferences a null JSCell.

Return jsUndefined() on the error paths so the property is reified to a
valid value while the exception still propagates. Also ensure the debug-only
error reporting re-throws the exception if it was cleared while printing.
@github-actions github-actions Bot added the claude label May 4, 2026
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Seg fault at address 0x5 #19650 - Segfault at near-null address when a lazy property callback (e.g. Bun.sql, Bun.$) throws during reifyStaticProperties due to stack overflow — exactly the null JSCell dereference this PR guards against

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #19650

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10b4726a-5be6-44ed-b1de-7ca00a0f68a7

📥 Commits

Reviewing files that changed from the base of the PR and between 1367067 and ff102fa.

📒 Files selected for processing (4)
  • src/jsc/bindings/BunObject.cpp
  • src/jsc/bindings/bindings.cpp
  • test/js/bun/util/BunObject.test.ts
  • test/js/bun/util/inspect.test.js
💤 Files with no reviewable changes (1)
  • src/jsc/bindings/BunObject.cpp

Included review availability: Your plan includes up to 5 reviews per rolling hour; 0 remain after this review.


Walkthrough

Changes

Bun SQL initialization no longer reports handled exceptions through debug-only event-loop blocks. Property and prototype inspection clears lookup exceptions and skips unavailable values. Regression tests cover proxy traps and failing lazy properties.

Bun exception handling

Layer / File(s) Summary
BunSql exception reporting cleanup
src/jsc/bindings/BunObject.cpp
Removes debug-only exception reporting from Bun SQL object creation while retaining existing exception checks.
Safe property and prototype traversal
src/jsc/bindings/bindings.cpp
Clears property lookup and proxy prototype exceptions, skips missing properties, and stops safely at null prototypes.
Regression coverage for inspection and lazy properties
test/js/bun/util/inspect.test.js, test/js/bun/util/BunObject.test.ts
Tests throwing proxy traps and lazy property failures, including continued inspection and clean subprocess termination.

Possibly related PRs

  • oven-sh/bun#37001: Removes the same Bun SQL debug exception-reporting blocks and adds overlapping lazy-property regression coverage.
  • oven-sh/bun#37175: Modifies the same exception-handling paths and inspection regression tests.
  • oven-sh/bun#39380: Covers matching property and prototype exception clearing in the same implementation and tests.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main fixes for Bun.inspect Proxy prototype failures and SQL lazy-property exception reporting.
Description check ✅ Passed The description explains the problems, fixes, scope, regression tests, and verification results in sufficient detail.

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Fix null JSCell deref in Bun lazy property callbacks and forEachProperty #29671 - Fixes the same null JSCell deref in defaultBunSQLObject/constructBunSQLObject plus other lazy property callbacks and forEachProperty
  2. Fix null deref in Bun object lazy PropertyCallbacks on exception #29807 - Fixes the same null deref in Bun object lazy PropertyCallbacks on exception, including the sql callbacks
  3. Fix null pointer dereference in Bun.sql when module fails to load #27308 - Fixes the same null pointer dereference in Bun.sql when module fails to load
  4. Fix null pointer crash in Bun.$ lazy property callback #27294 - Fixes null pointer crash in Bun.$ lazy property callback, also patches the sql callbacks with jsUndefined() returns
  5. Fix null pointer crash in lazy property reification during deepEquals #28550 - Fixes null pointer crash in lazy property reification, including the same sql callback fixes
  6. Fix null pointer deref in Bun.$ lazy init during stack overflow #28413 - Fixes null pointer deref in lazy init during stack overflow, including the same sql callback fixes
  7. fix(bindings): prevent null cell crash in PropertyCallback handlers on exception #27307 - Prevents null cell crash in PropertyCallback handlers on exception, including the sql callbacks
  8. Fix null pointer crash in reifyStaticProperty for BunObject PropertyCallbacks #28368 - Wraps PropertyCallbacks including the sql callbacks in null-safe wrappers via reifyStaticProperty

🤖 Generated with Claude Code

Comment thread src/jsc/bindings/BunObject.cpp Resolved
…ypes

- constructBunShell (Bun.$) had the same empty-JSValue-from-PropertyCallback
  issue as Bun.sql, crashing in putDirect when stack overflow throws during
  the shell builtin call. Return jsUndefined() on the error paths.
- Coalesce empty returns from the Zig-backed BunObject lazy property wrappers
  to jsUndefined() for the same reason.
- In forEachPropertyImpl, getPrototype on a Proxy can throw (getPrototypeOf
  trap) and return an empty JSValue; calling getObject() on it dereferences
  a null JSCell. Clear the exception and stop walking the chain. Also clear
  exceptions from getPropertySlot before the early continue so a pending
  exception does not leak into the next getPrototype call.

Fixes #19650
@robobun robobun changed the title Fix null deref in Bun.sql/Bun.SQL lazy property callbacks Fix null JSCell derefs in Bun object lazy init and Bun.inspect with Proxy prototypes May 4, 2026
@robobun

robobun commented May 4, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:48 PM PT - Aug 16th, 2026

✅ @robobun, your commit ff102fa80f306e7b35f49190b041724a67c9e40c passed in Build #99851! 🎉


🧪   To try this PR locally:

bunx bun-pr 30245

That installs a local version of the PR into your bun-30245 executable, so you can run:

bun-30245 --bun

Comment thread src/jsc/bindings/bindings.cpp Resolved
robobun added 2 commits May 4, 2026 18:02
JSC's reifyStaticProperty always stores the PropertyCallback result via
putDirect, and JSValue::get then asserts (!scope.exception() || !hasSlot)
under EXCEPTION_SCOPE_VERIFICATION. Returning jsUndefined() with a pending
exception satisfies putDirect but trips that assertion on the LLInt path.

Instead, catch the exception, report it via reportUncaughtExceptionAtEventLoop
(so the user sees why initialization failed), clear it, and return
jsUndefined(). The property is reified to undefined and subsequent accesses
return undefined without crashing.

Also covers the case where the second access would previously crash in
release builds because an empty JSValue had been cached in the slot.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/jsc/bindings/BunObject`+exports.h:
- Around line 96-105: The wrapper macro BunObject_lazyPropCb_wrap_##name
currently clears any exception from the Zig lazy getter (called via
BunObject_lazyPropCb_##name) then returns undefined, swallowing the error and
caching undefined; modify the wrapper to report the pending exception to the JS
engine before clearing it (i.e., call the same exception reporting used in the
explicit callbacks in BunObject.cpp while the DECLARE_THROW_SCOPE(vm) scope has
an exception), then call scope.tryClearException() and return undefined only
after reporting; keep the rest of the flow (decode result, return result or
jsUndefined) intact.

In `@test/regression/issue/19650.test.ts`:
- Around line 9-30: Replace the duplicate parameterized matrices by wrapping
both related tests in a single describe.each([...]) over the same key set and
move the existing test.concurrent.each(...) bodies inside that describe block as
individual test.concurrent(...) or test(...) cases; locate the current
test.concurrent.each invocation (the one iterating ["$", "sql", "SQL",
"postgres"]) and the matching block on lines 32-51 and change them to a single
describe.each that iterates the key array, with each iteration defining the two
test cases that previously duplicated the matrix, preserving existing test
names, scopes, and use of Bun.spawn/proc.exited and assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1f9fb9d2-14aa-4cd7-9c32-695e8cdba72d

📥 Commits

Reviewing files that changed from the base of the PR and between da65188 and aad7f3e.

📒 Files selected for processing (3)
  • src/jsc/bindings/BunObject+exports.h
  • src/jsc/bindings/BunObject.cpp
  • test/regression/issue/19650.test.ts

Comment thread src/jsc/bindings/BunObject+exports.h Resolved
Comment thread test/regression/issue/19650.test.ts Resolved
robobun added 2 commits May 12, 2026 07:40
The previous test only checked for a null signal and exit code in {0,1}.
Under the debug/UBSan build, the unfixed code hits a null-pointer member
call, prints a UBSan diagnostic, and exits with code 1 - which the test
accepted. The test now requires the subprocess to write 'OK' to stdout
after the access pattern, which the unfixed build never reaches on any
configuration (segfault on release, UBSan abort on debug).
@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main (23 commits behind) and tightened the regression test: it now requires the subprocess to write OK to stdout after the access pattern completes. Previously the test only checked signalCode === null && exitCode ∈ {0,1} — under the debug/UBSan build, the unfixed code exits with code 1 after the null-pointer diagnostic, so the old test passed without the fix.

Verified locally:

  • origin/main src + bun bd test 19650 → 8/8 fail (stdout empty)
  • PR src + bun bd test 19650 → 8/8 pass
  • USE_SYSTEM_BUN=1 bun test 19650 → 8/8 fail (SIGILL)

Previous CI run's only non-expired failures were node-http-backpressure-max, test-http-should-emit-close-when-connection-is-aborted, and hot.test.ts — none touch BunObject.cpp / bindings.cpp.

@robobun

robobun commented May 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status: build 56705 at ae3fc29 — all failures unrelated to this PR's code paths. Main at the same merge base (0b20408, build 56565) is green.

Error-level:

  • x64-musl-baseline build-rust — rustup toolchain download timed out (static.rust-lang.org … tcp connect error: Operation timed out). Infra flake.
  • test/js/node/test/parallel/test-stream-readable-setEncoding-existing-buffers.js on Windows 11 aarch64 — Illegal instruction at 0x7FF7E42D878C with ntdll.dll frames in the trace. Pure node:stream Readable.setEncoding test; passes locally on Linux x64 and touches none of the files this PR changes.

Warning-level (flaky, passed on retry): jsc-stress.test.ts Wasm mprotect failed: 487 on Windows (appeared on every build of this PR), 30205.test.ts napi addon on x64-asan.

This PR only modifies BunObject.cpp/BunObject+exports.h (Bun.* lazy PropertyCallbacks), bindings.cpp (forEachPropertyImpl), BunObject.rs (get_valkey_default_client), inspect.test.js, and 19650.test.ts. None of those intersect with rustup, node:stream, JSC Wasm JIT, or napi cleanup hooks.

Regression tests (19650.test.ts 9/9, inspect.test.js 74/74) pass on all shards where the build completed.

The fuzzer has reported 120+ variants of this fingerprint against main — all verified fixed by this PR's changes. Ready for review.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior feedback is addressed and I found no new issues, but this changes exception-handling semantics for several core Bun.* lazy initializers (permanently reifying to undefined on init failure and reporting via the event loop) and overlaps with several other open PRs for #19650 — worth a human look to confirm this is the approach to land.

Extended reasoning...

Overview

This PR fixes two distinct null-JSCell dereference crashes:

  1. BunObject.cpp / BunObject+exports.h — PropertyCallback entries (Bun.$, Bun.sql, Bun.SQL, Bun.postgres, plus the Zig-backed wrapper macro) previously returned an empty JSValue when initialization threw. JSC's reifyStaticProperty passes that straight to putDirect, which null-derefs. The fix catches the exception, reports it via reportUncaughtExceptionAtEventLoop, clears it, and returns jsUndefined() so the property is reified to a valid value.
  2. bindings.cpp forEachPropertyImpl — the prototype-walk in Bun.inspect called .getObject() on the result of getPrototype() without checking for a Proxy-trap exception, and didn't clear an exception from getPropertySlot on the not-found branch. Both are now guarded with CLEAR_IF_EXCEPTION and an empty-value check.

Tests are added in inspect.test.js (in-process Proxy-trap cases) and a new test/regression/issue/19650.test.ts (subprocess crash repros for all four lazy keys × two trigger scenarios).

Security risks

None identified. This is defensive crash hardening — no new attack surface, no auth/crypto/permissions, no user-controlled data paths beyond what already existed. The change makes hostile inputs (throwing Proxy traps, clobbered globals, near-stack-overflow) less dangerous, not more.

Level of scrutiny

Moderate-to-high. While the diff is not large (~80 lines of C++), it touches JSC exception-scope semantics in core runtime initialization for high-traffic APIs. Specific judgment calls a maintainer should weigh:

  • Permanently caching undefined: after a transient init failure (e.g. stack overflow on first access), Bun.sql / Bun.$ etc. become undefined for the rest of the process. That's clearly better than a segfault, but it's a user-visible behavior choice vs. e.g. leaving the property un-reified for retry.
  • reportUncaughtExceptionAtEventLoop from inside a property getter: this fires the global uncaught-exception handler synchronously during a property access, which is unusual.
  • constructBunShell no longer throws TypeError("Internal error: ...") on the non-object branches — it now silently returns undefined. Probably fine (those branches were unreachable in practice), but it's a behavior change beyond the crash fix.
  • Zig wrapper macro intentionally does not report before clearing (discussed in-thread; rationale is those getters don't run JS so only termination exceptions can land there).

Other factors

  • The find-duplicate-prs bot lists eight other PRs targeting the same crash class (#29671, #29807, #27308, #27294, #28550, #28413, #27307, #28368). A maintainer should confirm this is the variant they want to merge and close/rebase the others.
  • All my prior inline feedback (use test.concurrent.each, fix constructBunShell too) was applied; the napi.cpp sibling was explicitly deferred to a follow-up, which is reasonable.
  • CodeRabbit's suggestion to report exceptions in the Zig wrapper macro was declined with a reasoned response that CodeRabbit accepted; that thread is still technically unresolved but settled.
  • CI build #53645 was triggered for the latest commit; status not visible in the timeline.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found, but this touches JSC exception-scope handling across several core PropertyCallbacks and changes failure semantics (init exceptions are now reported-then-swallowed and the property is permanently reified to undefined), so it's worth a human sign-off on that tradeoff.

Extended reasoning...

Overview

This PR fixes two fuzzer-found null-JSCell dereferences:

  1. BunObject.cpp / BunObject+exports.h — the PropertyCallback lazy initializers for Bun.$, Bun.sql, Bun.SQL, Bun.postgres, and the Zig-backed wrapper macro now catch exceptions thrown during initialization, report them via reportUncaughtExceptionAtEventLoop, clear them, and return jsUndefined() instead of an empty JSValue. Previously the empty value flowed into reifyStaticProperty → putDirect, which dereferenced a null cell.
  2. bindings.cpp forEachPropertyImpl — the prototype-chain walk now clears exceptions after getPropertySlot (on the not-found branch too) and after getPrototype, and null-guards the result before calling .getObject(), so a throwing Proxy getPrototypeOf/get trap no longer crashes Bun.inspect.

Tests: two new Bun.inspect Proxy-trap cases in inspect.test.js, and a new test/regression/issue/19650.test.ts that spawns 8 subprocesses exercising the stack-overflow and clobbered-Symbol reproducers against each lazy key and asserts the child writes OK and exits without a signal.

Security risks

None identified. The change strictly hardens error paths (turning crashes into defined behavior) and doesn't touch auth, crypto, permissions, or input parsing. Exceptions are still surfaced to the user via the uncaught-exception reporter.

Level of scrutiny

Medium-high. This is production-critical native code at the JSC↔Bun boundary — ThrowScope discipline is subtle (the PR description itself notes that returning jsUndefined() with a pending exception trips an EXCEPTION_ASSERT). The fix is well-reasoned and consistent across all four callbacks plus the macro, and the regression tests are tight (require positive OK output, verified to fail on main). But there's a real behavioral decision baked in: a failed lazy init now permanently caches undefined for that property rather than throwing on access, and constructBunShell drops its previous throwTypeError("Internal error: ...") paths in favor of silently returning undefined. That's almost certainly the right call given JSC's PropertyCallback contract, but it's the kind of semantic choice a maintainer should ratify.

Other factors

  • All prior inline review comments (mine and CodeRabbit's) are resolved; the author applied the constructBunShell and test.concurrent.each suggestions and gave reasoned pushback on the two declined ones.
  • The duplicate-PR bot lists 8 other open/closed PRs targeting the same bug class — a human should confirm this is the variant to land.
  • CI passed on the ASAN shard for the new tests; remaining failures are documented as unrelated Windows flakes.
  • The napi.cpp instance of the same pattern was intentionally left for a follow-up (acknowledged in-thread).

Comment thread test/js/bun/util/inspect.test.js Resolved
Comment thread src/jsc/bindings/BunObject+exports.h Resolved
- inspect.test.js: the 'throwing getter' test used an accessor on the
  Proxy target with an empty handler, so getPropertySlot filled the slot
  as a GetterSetter without invoking the getter and the test passed on
  pre-PR code. Replace it with a throwing Proxy 'get' trap so performGet
  itself throws and getPropertySlot returns false with a pending
  exception, exercising the CLEAR_IF_EXCEPTION reorder.

- get_valkey_default_client: createNoJsNoPubsub throws a plain TypeError
  when REDIS_URL is empty/invalid. The C++ wrapper clears the pending
  exception and reifies Bun.redis to undefined, so the diagnostic was
  dropped. Report it via report_active_exception_as_unhandled (mirroring
  s3_default_client) and return UNDEFINED to satisfy to_js_host_call's
  'empty iff pending exception' assertion. Add regression test.
Comment thread src/jsc/bindings/BunObject+exports.h Outdated
Comment on lines +96 to +99
// PropertyCallbacks are reified by JSC::reifyStaticProperty, which hands the
// result straight to putDirect without checking for an exception, so a getter
// that threw has its exception folded here: report it, then reify the slot as
// undefined rather than an empty JSValue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed to one line in 6ecdfcd; the reasoning is in the PR description.

Comment thread src/jsc/bindings/BunObject.cpp Outdated
Comment on lines +318 to +322
// JSC's reifyStaticProperty passes the PropertyCallback result straight to
// putDirect without checking for exceptions, and JSValue::get asserts
// (!scope.exception() || !hasSlot). So these callbacks must not leave a
// pending exception; if loading the module fails we report it and return
// jsUndefined() so the property is reified to a valid value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed to one line in 6ecdfcd; the reasoning is in the PR description.

Comment thread src/jsc/bindings/BunObject.cpp Resolved
…PropertyCallback

The pinned WebKit's setUpStaticFunctionSlot skips the put when a
PropertyCallback returns empty and reports the slot as absent, so the
builders' exceptions propagate to the read and the slot stays unreified.
Clearing and reporting them here turned those throws into undefined and
broke builtin-esm-lazy-exports.test.ts.

What is left of that part is removing the debug-only report inside the
sql/SQL/postgres builders: it re-enters the error reporter in the middle
of the property lookup with the exception still pending, which aborts a
debug build on a Structure assertion.
@robobun robobun changed the title Fix null JSCell derefs in Bun object lazy init and Bun.inspect with Proxy prototypes Fix Bun.inspect null deref with Proxy prototypes; stop reporting from inside the sql lazy-property builders Aug 14, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — 5414053 addresses the earlier 🔴: the lazy-init wrappers, BunObject+exports.h/BunObject.rs hunks, and 19650.test.ts are gone, and defaultBunSQLObject/constructBunSQLObject are back to plain RETURN_IF_EXCEPTION(scope, {}) so the read throws catchably via the JSC fix. The remaining forEachPropertyImpl change and inspect tests look right.

One thing still outstanding from that comment: the PR title/description still describe the removed Part 1 approach (report-as-uncaught + reify to undefined) and claim "Fixes #19650" — please update before merge so the squash message matches what actually lands.

Extended reasoning...

Overview

Since my last review (2026-08-14T15:33Z 🔴), commit 5414053 dropped the superseded Part 1 changes. The diff is now 4 files / ~85 lines: bindings.cpp reorders CLEAR_IF_EXCEPTION before the !found continue and null-guards getPrototype()'s result in forEachPropertyImpl; BunObject.cpp removes two debug-only reportUncaughtExceptionAtEventLoop calls that are now wrong (the exception propagates catchably); inspect.test.js adds two Proxy-prototype crash tests; BunObject.test.ts adds a subprocess test locking in the throw-and-stay-unreified behavior.

Security risks

None. Defensive exception clearing / null guard in an inspection path; no auth, crypto, or untrusted-input parsing.

Level of scrutiny

Medium — bindings.cpp is core JSC glue, but the change is a small defensive guard following the same CLEAR_IF_EXCEPTION pattern already used a few lines away, and robobun's 2026-08-13 triage confirms this crash is still live on main.

Other factors

  • The code half of my previous 🔴 is fully addressed; the description half (drop the Part-1 prose and "Fixes #19650") is not, so the squash-merge message would be misleading.
  • robobun previously noted #30099 and #29642 target the same inspect crash — a maintainer should pick one.
  • One nit (undrained piped stderr in the new BunObject.test.ts subprocess test) is posted inline; not blocking.
  • I checked that proto.getObject() is safe when proto is a non-empty non-object primitive (returns nullptr, terminating the loop), and that the removed #if BUN_DEBUG blocks were the only remaining Part-1 residue in BunObject.cpp.

Comment thread test/js/bun/util/BunObject.test.ts Resolved
@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Title and description were rewritten for the reduced scope (no #19650 claim) shortly before the last review ran, so that part is already done; the stderr nit is fixed in 230e894. Current diff is the forEachPropertyImpl guard plus removing the debug-only report in the two sql builders, with a test for each.

Comment thread test/js/bun/util/BunObject.test.ts Resolved
Comment thread test/js/bun/util/inspect.test.js Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — both remaining nits from the last pass are addressed in 2a20b8d.

What was reviewed:

  • forEachPropertyImpl (bindings.cpp): the CLEAR_IF_EXCEPTION reorder before the !found continue and the empty-proto guard match the fast-path pattern at ~5583; no other unguarded getPrototype().getObject() sites in that function.
  • BunObject.cpp: pure deletion of the two #if BUN_DEBUG reports; defaultBunSQLObject/constructBunSQLObject now propagate identically to constructBunShell.
  • Tests: both inspect cases now pin exact output; the lazy-property subprocess test drains stdout/stderr/exited concurrently and asserts the combined object; the comment now attributes $ to the shell builtin correctly.
Extended reasoning...

Overview

Four files. Two native hunks: (1) src/jsc/bindings/bindings.cpp reorders a CLEAR_IF_EXCEPTION in forEachPropertyImpl's slow path so a throwing Proxy get trap is cleared before the early continue, and adds CLEAR_IF_EXCEPTION + an empty check after getPrototype() so a throwing getPrototypeOf trap doesn't null-deref through .getObject(). This mirrors the existing fast-path handling a few lines above (bindings.cpp:5574-5584). (2) src/jsc/bindings/BunObject.cpp deletes two #if BUN_DEBUG blocks that called reportUncaughtExceptionAtEventLoop from inside the sql/SQL/postgres lazy-property builders while an exception was pending — that re-entered JS mid-lookup and tripped a debug-build assertion. The builders now propagate via RETURN_IF_EXCEPTION(scope, {}) exactly as constructBunShell already does, and JSC's fixed reifyStaticProperty handles the empty result. Tests: two new Bun.inspect cases in inspect.test.js (both crash on main) and one subprocess test in BunObject.test.ts covering the debug-only abort.

Security risks

None. This is exception-handling in the inspector's property walk and removal of debug-only diagnostic code. No auth, crypto, permissions, or untrusted-input parsing paths are touched. CLEAR_IF_EXCEPTION in forEachPropertyImpl is the established policy for user-code side effects during formatting (getters, traps) and is already used for the same trap on the fast path.

Level of scrutiny

Medium — native JSC bindings, but the change is mechanically small and pattern-matched against existing code in the same function. The bindings.cpp hunk is a textbook "exception check after every call that can enter JS" fix (REVIEW.md's most-blocked category), and the BunObject.cpp hunk is pure deletion of debug-only code that made the sql builders inconsistent with the shell builder. No new control flow, no allocation/ownership changes, no API surface change.

Other factors

This PR has been through many review rounds since May. The larger "part 1" (lazy-property wrappers in BunObject+exports.h / BunObject.rs) was correctly identified as superseded by oven-sh/WebKit#282 and removed in 5414053; the current diff is the minimal remainder. All prior inline feedback (mine and comment-cop's) is addressed: stderr is now drained (230e894), the inspect tests pin exact output instead of .not.toThrow(), and the test comment correctly distinguishes the $ shell-builtin path from the bun:sql require path (2a20b8d). The bug hunting system found nothing this run. No human reviewer has open concerns on the thread.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the reduced scope addresses everything from the earlier rounds.

What was reviewed:

  • forEachPropertyImpl reordering: CLEAR_IF_EXCEPTION now runs before the !found continue, and the getPrototype result is null-checked before .getObject() — matches the fast-path pattern at line ~5584.
  • BunObject.cpp: removing the debug-only reportUncaughtExceptionAtEventLoop calls leaves RETURN_IF_EXCEPTION(scope, {}) in place, so the sql builders now propagate the same way constructBunShell already does.
  • Tests: both Proxy cases pin exact output; both subprocess tests drain stdout/stderr/exited concurrently and assert the combined object; the new inspect(Bun) walk test exercises the getPropertySlot reordering via the non-Proxy path.
Extended reasoning...

Overview

Four files touched. Production changes are ~7 lines net: (1) bindings.cpp — reorder getPropertySlot result handling in forEachPropertyImpl so CLEAR_IF_EXCEPTION runs before the early continue, and guard getPrototype().getObject() against an empty result from a throwing Proxy getPrototypeOf trap; (2) BunObject.cpp — delete two #if BUN_DEBUG blocks that called reportUncaughtExceptionAtEventLoop from inside the sql/SQL/postgres lazy-property builders, mid-lookup with the exception still pending. Three new tests in inspect.test.js and one in BunObject.test.ts.

Security risks

None. This is a crash-hardening change in the property-enumeration path of Bun.inspect. No auth, crypto, permissions, or untrusted-input parsing is touched. The added CLEAR_IF_EXCEPTION calls follow the same "ignore trap exceptions during inspection" policy already used a few lines above (line ~5584) and at ~5668-5671, so this does not introduce new exception-swallowing surface.

Level of scrutiny

Low-to-moderate. The bindings.cpp hunk is a mechanical null-guard + exception-clear that mirrors the existing fast-path branch in the same function. The BunObject.cpp hunk deletes debug-only code with no release-build effect; the exception is still propagated by the RETURN_IF_EXCEPTION(scope, {}) immediately after, and the pinned WebKit's reifyStaticProperty now handles the empty return correctly (verified in the 2026-08-14 review round).

Other factors

This PR has been through many review rounds. My 2026-08-14 blocking comment asked for the tryClearException()+jsUndefined() wrappers to be dropped since they regressed main's now-correct catchable-throw behavior — that was done in 5414053. The stderr-drain nit (230e894), the .not.toThrow() → .toBe("{}") strengthening, and the $-vs-bun:sql comment clarification (both 2a20b8d) were all applied. The most recent commit (bafa86f) adds the inspect(Bun) walk test, which exercises the getPropertySlot reordering through the non-Proxy lazy-property path — a good complement to the two Proxy tests. All subprocess tests follow the REVIEW.md convention (Promise.all on stdout/stderr/exited, combined-object assertion). No outstanding reviewer comments remain.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the remaining diff is small and mechanical, and every prior review thread has been addressed.

What was reviewed:

  • forEachPropertyImpl (bindings.cpp): the CLEAR_IF_EXCEPTION reorder before continue and the getPrototype empty-value guard both mirror the fast-path pattern at ~line 5584 in the same function.
  • BunObject.cpp: only debug-gated lines removed; the resulting RETURN_IF_EXCEPTION(scope, {}) shape now matches constructBunShell exactly, and the WebKit-side fix (reifyStaticProperty skipping empty results) is what handles propagation.
  • Tests: subprocess tests drain both pipes and assert {stdout, stderr, exitCode}; the two Proxy tests pin exact output; the third inspect test's require("node:util") preload and the reason for it are documented.
Extended reasoning...

Overview

Four files touched. src/jsc/bindings/bindings.cpp reorders one CLEAR_IF_EXCEPTION call so it runs before an early continue when getPropertySlot returns false, and guards the getPrototype(...).getObject() call against an empty JSValue when a Proxy getPrototypeOf trap throws — both changes follow the identical pattern already present in the fast path ~100 lines earlier in the same function. src/jsc/bindings/BunObject.cpp deletes two three-line #if BUN_DEBUG blocks in defaultBunSQLObject / constructBunSQLObject that called reportUncaughtExceptionAtEventLoop from inside a PropertyCallback, mid-lookup, with an exception still pending; the resulting shape is byte-identical to how constructBunShell already handles the same failure. Tests are additive: three cases in inspect.test.js and one in BunObject.test.ts.

Security risks

None. This is object-inspection formatting and debug-only diagnostic removal. No auth, crypto, filesystem, or network paths. The Bun.inspect change makes existing exception-swallowing behavior consistent (the fast path already CLEAR_IF_EXCEPTIONs Proxy trap throws) rather than introducing new swallowing.

Level of scrutiny

Native JSC bindings warrant care, but the actual change here is minimal: 8 lines in bindings.cpp that follow an established in-file pattern, and 6 debug-gated lines deleted from BunObject.cpp with zero release-build effect. This PR has been through many review rounds since May; earlier revisions carried a much larger BunObject+exports.h / BunObject.rs workaround that was correctly identified as regressing main once oven-sh/WebKit#282 landed, and was dropped in 5414053. What remains is the narrow, uncontroversial residue.

Other factors

Every prior review thread is resolved. My last two nits (test-comment accuracy for $ vs bun:sql, and pinning exact output over .not.toThrow()) were addressed in 2a20b8d. The two commits since then (bafa86f, bfb3414) add the third inspect test and preload node:util so Bun.env's Windows custom-inspect doesn't interfere once Symbol is clobbered — the test comment explains why. The subprocess tests follow REVIEW.md conventions (concurrent pipe drain, combined-object assertion, bunEnv, await using). The BunObject.test.ts case pins the WebKit-side contract (throwing builder → catchable TypeError on read, slot stays unreified) so a future re-introduction of the debug-only report or a WebKit regression would be caught. The PR no longer claims #19650 and correctly cross-references #37714/#37258 for the adjacent redis/process.* cases it does not touch.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@robobun

robobun commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

For whoever picks this up, the overlapping open PRs for this crash family, so only one needs to land:

This PR is the minimal version of the forEachPropertyImpl fix plus the deletion. If #37175 is preferred, this one can be closed once it lands; if the smaller change is preferred, this is it.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — both nits from the last round are addressed and the diff is now the minimal fix.

What was reviewed:

  • forEachPropertyImpl in bindings.cpp: the CLEAR_IF_EXCEPTION reorder before continue and the empty-proto guard match the fast-path pattern a few lines up; traced the Proxy get-trap and getPrototypeOf-trap flows through to the test assertions.
  • BunObject.cpp: pure deletion of the two #if BUN_DEBUG mid-builder reports; constructBunShell already behaves this way.
  • Tests: subprocess pipes are drained concurrently, assertions are exact-value, and the earlier concerns (comment attribution for $, .toBe("{}") over .not.toThrow()) are applied.
Extended reasoning...

Overview

Four files touched. src/jsc/bindings/bindings.cpp reorders an existing CLEAR_IF_EXCEPTION in forEachPropertyImpl so it runs before the if (!found) continue, and guards getPrototype()'s result against empty before calling .getObject() (with the same clear). src/jsc/bindings/BunObject.cpp deletes two #if BUN_DEBUG blocks that called reportUncaughtExceptionAtEventLoop from inside defaultBunSQLObject/constructBunSQLObject while an exception was pending mid-lookup. Three new inspect.test.js cases and one new BunObject.test.ts case cover the fixes.

Security risks

None. Bun.inspect already swallows exceptions from getters and traps at multiple points in this function; the change moves one clear earlier and adds one to match the fast path. No new user-controlled data flows, no auth/crypto/permissions.

Level of scrutiny

Medium — native JSC bindings, but the change is mechanical: a reorder of an existing macro, an empty-value guard identical to the pattern already used a few lines above, and removal of debug-only code. The PR has been through six review rounds; the earlier scope (which regressed reifyStaticProperty's new behavior) was correctly cut after the WebKit bump, and the current diff is the residue that still crashes/aborts on main.

Other factors

All prior feedback from this reviewer is resolved: the blocking regression (superseded BunObject wrappers reifying undefined) was removed in 5414053; the stderr-drain and combined-object assertion were applied in 230e894; the $-vs-bun:sql comment attribution and the .toBe("{}") strengthening were applied in 2a20b8d. The bug-hunting system found nothing this run. The one known caveat — BunObject.test.ts only fails on debug builds of main because the removed report was #if BUN_DEBUG-gated — is stated in the PR description and is inherent to a debug-assertion fix; CI runs debug lanes.

@robobun

robobun commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as a duplicate of #29642, which fixes the same stale-exception / getPrototype null dereference in forEachPropertyImpl. The BunObject.cpp debug-block removal and the BunObject.test.ts regression test from this PR have been folded into #29642.

@robobun robobun closed this Aug 17, 2026
dylan-conway pushed a commit that referenced this pull request Aug 18, 2026
…p or getPrototypeOf throws during the property walk (#29642)

### Problem

- `Bun.inspect()`, `console.log()` and `expect()` failure output crash
with `Segmentation fault at address 0x5` (debug builds: UBSan "member
call on null pointer of type 'JSC::JSCell'" in `JSCJSValueCell.h`) when
a property lookup throws while an object is being formatted. Release
repro:
  ```js
const proto = new Proxy({ a: 1 }, { getPrototypeOf() { throw new
Error("boom"); } });
  console.log(Object.create(proto));
  ```
and likewise with a Proxy whose `get` trap (or a getter reached through
a Proxy) throws for one property.
- The same happens with a lazily initialized property of the `Bun`
object whose initializer throws (fuzzer sample: `globalThis.Symbol`
replaced, then `Bun.inspect(Bun)`; the builtin behind `Bun.$` calls
`Symbol("cwd")`). Release builds print `Bun` with most of its properties
missing (72 of 115 on the shipped build); debug builds abort with
`ASSERTION FAILED: Unexpected exception observed` / `Symbol is not a
function. (In 'Symbol("cwd")' ...)` when the next lazy property is
built.
- Plain-JavaScript variant of the same leak: `console.log` of a module
namespace during an import cycle, when one export is still in its
temporal dead zone, throws `ReferenceError: Cannot access 'x' before
initialization` out of `console.log` (the stale exception is picked up
while the next export is formatted). `util.inspect` prints such an
export as `<uninitialized>`.
- Cause, in the slow path of `JSC__JSValue__forEachPropertyImpl`
(`src/jsc/bindings/bindings.cpp`):
- `object->getPropertySlot()` reports a throwing Proxy trap, a throwing
lazy initializer or a TDZ namespace export as "not found" with the
exception still pending, and the loop `continue`d before the
`CLEAR_IF_EXCEPTION` below it. The following lookups and formatting
callbacks then run with that exception pending (the dropped properties,
the rethrown `ReferenceError`, the debug assertion).
- When the walk moves to the next prototype,
`iterating->getPrototype(globalObject).getObject()` runs `getObject()`
on the empty `JSValue` that `getPrototype` returns when it threw (either
because of the stale exception above or because the `getPrototypeOf`
trap itself throws). The empty value passes `isCell()`, so this reads
the type byte of a null cell: the fault at address 5.
- `napi_get_all_property_names` (`src/jsc/bindings/napi.cpp`, descriptor
filter loop) has the same `getPrototype().getObject()` chain after an
unchecked `getOwnPropertyDescriptor`, so a Proxy trap throwing there
returned `napi_ok` with an exception pending in own-only mode and
segfaulted in include-prototypes mode.
- `defaultBunSQLObject` / `constructBunSQLObject`
(`src/jsc/bindings/BunObject.cpp`) had a debug-only block that handed a
sql module load failure to `reportUncaughtExceptionAtEventLoop` while
the exception was still pending on the VM, so `globalThis.Symbol = NaN;
Bun.sql` (and the fuzzer sample above, once the walk gets past `Bun.$`)
aborted debug builds with `ASSERTION FAILED: ... object->structure() ==
this` in `Structure::storedPrototype` instead of throwing.

### Fix

- `bindings.cpp`: clear the exception after `getPropertySlot` regardless
of its result, which is what the ordered variant
`JSC__JSValue__forEachPropertyOrdered` already does; read the next
prototype into a `JSValue`, clear the exception and stop the walk when
it is empty. (An earlier revision also held the prototype being walked
under an `EnsureStillAliveScope`; dropped, since the raw pointer is used
after every call into JS in the loop body and so is live across them
anyway.)
- Behaviour change to note: a property whose lookup throws is now left
out of the output instead of the whole `console.log` / `Bun.inspect`
call throwing or crashing. For TDZ namespace exports this differs from
`util.inspect`'s `<uninitialized>`; printing that marker would be a
formatter feature on top of this fix and is not attempted here.
- `napi.cpp`: check for an exception after `getOwnPropertyDescriptor`
and after `getPrototype` and return `napi_pending_exception`, which is
what Node returns for these cases.
- `BunObject.cpp`: drop the debug-only report. The exception is
propagated to the reader by the `RETURN_IF_EXCEPTION` right below it, so
debug builds now behave like release builds (`Bun.sql` throws).
- Why this is the right place: the formatter deliberately swallows
errors thrown by individual properties (getters, traps) and prints the
rest of the object; these two sites were the only ones in the walk that
acted on a "not found" result or a prototype value before clearing the
exception that produced it. Skipping just the property (or stopping at
just the prototype) whose lookup threw is the existing behaviour for
every other throw site in this function.
- Verification:
- `test/js/bun/util/inspect.test.js`, "Bun.inspect when a property
lookup throws" (5 spawned cases): a Proxy `get` trap, a getter behind a
Proxy, a `getPrototypeOf` trap, a throwing lazy `Bun` property, and a
two-file import cycle with a TDZ export. Without the fix (shipped
release build and an unfixed debug build) all five fail: the Proxy
children segfault / fail UBSan, the `Bun` child prints
`[false,false,...]` on release and aborts on debug, the cycle child
exits 1 with the `ReferenceError`; with it each prints everything except
the one property whose lookup threw.
- `test/js/bun/util/BunObject.test.ts`, "a lazy property whose builtin
fails to load throws from the read": `Bun.$` / `sql` / `SQL` /
`postgres` with `Symbol` broken throw a `TypeError` on two consecutive
reads. Aborts on an unfixed debug build (the `BunObject.cpp` hunk);
passes on release either way, as the removed block is debug-only. The
fixture builds `process.env` before breaking `Symbol` because the `$`
builder reads it, and building it on Windows reifies another `Bun`
property mid-lookup, which on a Windows debug build would hit the
separate `storedPrototype` assertion that #37001 fixes (verified on
Linux only).
- `test/napi/napi.test.ts`: `getOwnPropertyDescriptor` trap throwing in
own-only and include-prototypes mode (compared against Node), and a
`getPrototypeOf` trap that throws on the second call so the check after
`getPrototype` is the one that fires.
- Repros above and the tests also run clean under
`BUN_JSC_validateExceptionChecks=1`.

### Background

- `forEachPropertyImpl` is the property walk behind Bun's native
formatter. It collects the property names of the object and of up to
five prototypes, looks each one up through the original object with
`getPropertySlot`, and hands the value to a callback that formats it.
Errors thrown by individual properties are swallowed on purpose so that
one bad getter does not make `console.log` throw.
- A JSC exception is "pending" state on the VM, not C++ unwinding. A
function that throws returns a failure value (`false`, or the empty
`JSValue`) and leaves the exception on the VM; until something clears or
rethrows it, most JSC entry points return early as soon as they are
called, and debug builds assert when a function that did not throw is
observed returning with an exception pending. `CLEAR_IF_EXCEPTION` drops
the pending exception.
- The empty `JSValue` (`JSValue()`) is encoded as 0. `isCell()` is true
for it, so `getObject()` on it dereferences a null cell pointer rather
than returning null; callers have to test the value itself first.
- Lazy properties of the `Bun` object are entries in a static property
table whose value is produced by a builder the first time the property
is read (`PropertyCallback`). Some builders evaluate built-in JavaScript
modules (the shell for `Bun.$`, the sql module for `Bun.sql`), so they
can throw when that module fails to evaluate, and JSC reports that to
the reader as "property not found" plus a pending exception.

### Consolidated duplicates

Found repeatedly by the fuzzer (fingerprint `d678cafe50a2ad6e`). Earlier
round, folded in here in April: #29071 #28991 #28919 #28918 #28882
#28854 #28530 #28325. This round, closed in favour of this PR: #30099
#30245 #37160 #37175 #37213 #37256 #37428 #38700 #38921 #39363 #39365
#39380 #39412 #39413 (and #39411, closed earlier). The `BunObject.cpp`
hunk and the `BunObject.test.ts` test come from #30245 / #37428; the
same hunk is also part of #37001, which fixes the underlying
`storedPrototype` assertion in JSC.

Related fixes that are not part of this bug and stay open on their own:
#39382 (a custom inspect function when `node:util` fails to load),
#37202 (`util.isError` with a throwing `getPrototypeOf` trap), #37331
(`forEachPropertyOrdered` when the callback throws), #37001 (stale
structure in `JSObject::getPropertySlot`), #32263 (additional checks in
the same napi loop).

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 11 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/util/inspect.test.js test/napi/napi.test.ts

<!-- robobun:evidence:end -->

---------

Co-authored-by: robobun <robobun@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant